/*******************************************************************************
 * Software Name : Android Simple Downloader
 *
 * Copyright © 2012 Rinvay Tang
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/

package com.rinvay.filedownload;

import java.io.File;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Vector;

import com.rinvay.filedownload.model.DownloadTask;
import com.rinvay.filedownload.model.Part;
import com.rinvay.network.http.HttpRequest;
import com.rinvay.network.http.conn.HttpConnection;
import com.rinvay.network.http.conn.HttpConnectionListener;
import com.rinvay.network.http.conn.impl.HttpConnectionManager;
import com.rinvay.network.http.exception.NetWorkInvalidException;
import com.rinvay.utils.FileUtil;
import com.rinvay.utils.logger.Logger;

/**
 * 下载管理
 * @author Rinvay.T
 * @date 2012-5-27
 * @time 下午10:10:29
 */
public class FileDownloadManager
{
    private static final String TAG = "FileDownloadManager";

    private HttpConnectionManager connMgr = null;
    /**
     * 任务列表
     */
    private ArrayList<DownloadTask> taskes = null;
    /**
     * 下载活动监听器
     */
    private Vector<FileDownloadListener> listeners = null;

    /**
     * 定时器运行间隔 ms
     */
    private static final int INTERVAL = 1000;
    /**
     * 定时器
     */
    private Timer timer = new Timer("FileDownloadTimer");

    public FileDownloadManager() {
        this(null);
        if (connMgr == null) {
            connMgr = new HttpConnectionManager();
        }
    }

    /**
     * 带外部HttpConnectionManager的下载管理器
     * @param connMgr
     */
    public FileDownloadManager(HttpConnectionManager connMgr) {
        taskes = new ArrayList<DownloadTask>();

        listeners = new Vector<FileDownloadListener>();

        this.connMgr = connMgr;
    }

    public ArrayList<DownloadTask> getResourceList() {
        return taskes;
    }

    /**
     * 创建下载任务
     * @param url 资源地址
     * @param fileDirectory 存放目录
     * @param fileName 文件名
     * @param threadSize 启动线程数
     * @return
     */
    public DownloadTask createResourse(String url, String fileDirectory,
            String fileName, int threadSize) {
        DownloadTask task = new DownloadTask(url, fileDirectory, fileName,
                threadSize);
        Logger.info(TAG, "create task:" + task);
        return task;
    }

    /**
     * 开始一个下载任务
     * 
     * @param r
     */
    public void startResourceDownload(final DownloadTask r) {
        Logger.info(TAG, "startResourceDownload " + r);

        if (!taskes.contains(r)) {
            taskes.add(r);
        }

        notifyProgressing(r.getUUId());

        new Thread(new Runnable() {

            public void run() {
                r.setState(DownloadTask.STATE_STARTED);
                try {
                    boolean succeed = FileUtil.createFile(r.getFileDirectory() + File.separator
                            + r.getFileName(), true);
                    Logger.info(TAG, "create file " + succeed);
                } catch (IOException e) {
                    e.printStackTrace();
                }

                int size = r.getSize();
                if (size == -1) {
                    r.setState(DownloadTask.STATE_CONNECTING);
                    size = initResourceSize(r);
                }

                //test case when size is -1
                //size = -1;
                Logger.info(TAG, "file size: " + size + " byte");
                r.setState(DownloadTask.STATE_DOWNLOADING);
                //按照资源大小和任务线程数，划分资源块
                if (size == -1) {
                    Logger.info(TAG, "size unknown, Task only one part");
                    Part p = new Part(r, 0, -1, 0);
                    r.getParts().add(p);
                    HttpConnection conn = new FileDownloadHttpConnection(p,
                            null);
                    conn.setName(r.getFileName() + "-part");
                    connMgr.startConnection(conn);
                } else {
                    Logger.info(TAG, "Task divides " + r.getThreadSize()
                            + " parts");
                    if (r.getThreadSize() < 1) {
                        r.setThreadSize(3);
                    }
                    int partSize = size / r.getThreadSize();

                    //整除有余数的话，校正
                    if (size - partSize * r.getThreadSize() > 0) {
                        partSize++;
                    }

                    for (int i = 0; i < r.getThreadSize(); i++) {
                        int length = partSize;
                        if (i == r.getThreadSize() - 1) {
                            length = size - partSize * i;
                        }
                        Part p = new Part(r, i * partSize, length, 0);
                        Logger.info(TAG,
                                "part " + (i + 1) + ": " + p.toString());
                        r.getParts().add(p);
                        HttpConnection conn = new FileDownloadHttpConnection(p,
                                connListener);
                        conn.setName(r.getFileName() + "-part-" + i);
                        connMgr.startConnection(conn);
                    }
                }

            }
        }).start();

        startTimer();

    }

    /**
     * 连接网络资源，查询文件的大小<br>
     * query the content-length of the resource
     * @param r
     * @return
     */
    private int initResourceSize(DownloadTask r) {
        Logger.info(TAG, "initResourceSize");
        int size = -1;
        try {
            HttpRequest request = new HttpRequest(r.getResourceUrl(), "HEAD");
            request.setHeaders(r.getConnProperties());
            HttpConnection conn = connMgr.createDefaultHttpConnection(request,
                    null);
            conn.setName("getSize");
            HttpURLConnection urlConn = conn.connect();
            size = urlConn.getContentLength();
            r.setSize(size);
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (NetWorkInvalidException e) {
            e.printStackTrace();
        }
        return size;
    }

    /**
     * 继续下载 
     * @param r
     */
    public void resumeResourceDownload(DownloadTask r) {
        Logger.info(TAG, "resumeResourceDownload " + r);
        if (r.getState() < DownloadTask.STATE_STARTED
                || r.getState() == DownloadTask.STATE_FINISHED) {
            //任务还没开始或已经完成
            startResourceDownload(r);
            return;
        }
        if (r.getState() == DownloadTask.STATE_CONNECTING
                && r.getState() == DownloadTask.STATE_DOWNLOADING) {
            //任务正在连接或下载
            Logger.info(TAG, "task is connecting or downloading " + r);
            return;
        }
        r.setState(DownloadTask.STATE_DOWNLOADING);
        for (int i = 0; i < r.getParts().size(); i++) {
            Part p = r.getParts().get(i);
            if (p.getState() != DownloadTask.STATE_FINISHED) {
                FileDownloadHttpConnection conn = new FileDownloadHttpConnection(
                        p, connListener);
                conn.setName(r.getFileName() + "-part-" + i);
                connMgr.startConnection(conn);
            }
        }
        startTimer();
    }

    public void pauseResouseDownload(DownloadTask r) {
        Logger.info(TAG, "pauseResouseDownload " + r);
        r.setState(DownloadTask.STATE_PAUSED);
    }

    public void deleteResouseDownload(DownloadTask r) {
        if (r.getState() >= DownloadTask.STATE_STARTED
                && r.getState() < DownloadTask.STATE_PAUSED) {
            pauseResouseDownload(r);
        }
        taskes.remove(r);
    }

    public DownloadTask findTaksByUUID(String uuid) {
        for (DownloadTask task : taskes) {
            if (uuid != null && uuid.equals(task.getUUId())) {
                return task;
            }
        }
        return null;
    }

    /**
     * 底层连接的监听器
     */
    private FileDownloadHttpConnectionListener connListener = new FileDownloadHttpConnectionListener() {

        public void onConnecting() {
        }

        public void onConnected() {
        }

        public void onTransProgress(String uuid) {
            notifyProgressing(uuid);
        }
        
        public void onTransError(String uuid) {
            DownloadTask task = findTaksByUUID(uuid);
            if(task.isTaskFailed()) {
                Logger.info(TAG, task.getFileName() + " task failed");
                notifyTransError(uuid);
            } else {
                Logger.info(TAG, task.getFileName() + " task not failed");
            }
        }

        public void onTransferred(String uuid) {
            DownloadTask task = findTaksByUUID(uuid);
            Logger.info(TAG, task.getFileName() + " onTransferred");
            if(task.updateTaskFinished()) {
                Logger.info(TAG, task.getFileName() + " finished");
                notifyTransferred(uuid);
            } else {
                Logger.info(TAG, task.getFileName() + "not finished");
//                notifyProgressing(uuid);
            }
        }

        public void onError(int errorcode, String errorMsg) {
        }
    };

    private void notifyProgressing(String uuid) {
        for (FileDownloadListener l : listeners) {
            l.onDownloadProcessing(uuid);
        }
    }

    private void notifyTransferred(String uuid) {
        for (FileDownloadListener l : listeners) {
            l.onDownloadTransferred(uuid);
        }
    }

    private void notifyTransError(String uuid) {
        for (FileDownloadListener l : listeners) {
            l.onDownloadFailed(uuid);
        }
    }

    private boolean isTimerStarted;

    private synchronized void startTimer() {
        if (isTimerStarted) {
            return;
        }
        /**
         * 定时任务,eg:计算下载速度
         */
        TimerTask task = new TimerTask() {

            public void run() {
                boolean isCalulated = caculateSpeeds(INTERVAL / 1000);
                if (!isCalulated) {
                    stopTimer();
                }
            }
        };
        timer = new Timer("FileDownloadTimer");
        timer.scheduleAtFixedRate(task, 0, INTERVAL);
        isTimerStarted = true;
    }

    private synchronized void stopTimer() {
        if (!isTimerStarted) {
            return;
        }
        timer.cancel();
        timer.purge();
        isTimerStarted = false;
    }

    /**
     * 计算任务下载速度
     * @param interval 计算周期（单位/秒）
     */
    private boolean caculateSpeeds(int interval) {
        boolean isCaculated = false;
        for (DownloadTask task : taskes) {
            if (task.getState() < DownloadTask.STATE_STARTED
                    || task.getState() > DownloadTask.STATE_DOWNLOADING) {
                //非活动状态的下载任务不用计算
                task.setSpeed(0);
                task.setPreLength(task.getCurLength());
                continue;
            }
            int curlength = task.getCurLength();
            int speed = ((curlength - task.getPreLength()) / 1024) / interval;
//			Log.i(TAG, "speed:" + speed + task.toString());
            task.setPreLength(curlength);
            task.setSpeed(speed);
            isCaculated = true;
        }
        return isCaculated;
    }

    public void addListener(FileDownloadListener listener) {
        if (!listeners.contains(listener)) {
            listeners.add(listener);
        }
    }

    public void removeListener(FileDownloadListener listener) {
        listeners.remove(listener);
    }

    public void removeAllListeners() {
        listeners.clear();
    }

}
